feat(chat): highlight-to-chat — reference file/table selections in Chat - #6087
feat(chat): highlight-to-chat — reference file/table selections in Chat#6087mzxchandra wants to merge 28 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Producers: Add to Chat (Blimp) in Monaco and Tiptap file editors and in the table grid context menu; Routing: Server: Reviewed by Cursor Bugbot for commit 7644b8d. Configure here. |
Greptile SummaryIDE-style highlight-to-chat for Files and Tables: selection-scoped
Confidence Score: 3/5Not fully safe to merge until multi-row table copy still attaches the selection MIME when the loaded-row chip path cannot run (empty loaded intersection or more than 500 selected rows). Multi-row table copy can still fall through to an async text/plain-only clipboard write when writeLoadedRowsWithChip declines, so paste into Chat never rebuilds a table_selection chip on those paths. The earlier always-overwrite MIME bug for ordinary in-cap multi-row copies is fixed. Files Needing Attention: apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/table-grid.tsx
|
| Filename | Overview |
|---|---|
| apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/table-grid.tsx | Adds table selection chips and a sync chip+copy path; multi-row paste still fails when the chip path declines and the async clipboard write is used. |
| apps/sim/lib/copilot/chat/process-contents.ts | Resolves file/table selections server-side with workspace checks, row re-fetch, and content bounds. |
| apps/sim/lib/copilot/chat/selection-clipboard.ts | Encodes/decodes selection contexts on the custom clipboard MIME with basic shape validation. |
| apps/sim/hooks/use-add-to-chat.ts | Live chip insert or workspace-scoped handoff navigation when Chat is not mounted. |
Sequence Diagram
sequenceDiagram
participant User
participant TableGrid
participant Clipboard
participant ChatInput
participant Server as processContextsServer
User->>TableGrid: Select rows/cells + Copy or Add to chat
alt Sync loaded copy within row cap
TableGrid->>Clipboard: text/plain + text/x-sim-selection
else Paged/async copy path
TableGrid->>Clipboard: text/plain only (ClipboardItem)
end
User->>ChatInput: Paste or live addMothershipContexts
ChatInput->>ChatInput: Chip table_selection (ids only)
ChatInput->>Server: Chat POST with contexts
Server->>Server: getRowsByIds + markdown table
Server-->>ChatInput: Resolved agent context
Reviews (17): Last reviewed commit: "fix(chat): compare selection ids as sets..." | Re-trigger Greptile
|
@cursor review |
|
@cursor review |
|
@cursor review |
|
@cursor review |
|
@cursor review |
|
@cursor review |
|
Follow-up on the previous round, now done rather than deferred. I said the copy-path helpers being module-private in a ~4,600-line component with no test file was a real gap — the last two fixes to that logic were reasoned, not covered, and both were wrong on the first attempt (the chip-vs-text cap conflation, and before it the loaded-rows narrowing). That is precisely the code that should not be untested.
Restoring the old No behavior change in this commit — it is the same logic, relocated and covered. |
|
@cursor review |
…under-counting rows Three findings from one Bugbot round. Hidden columns widened cell ranges (reported twice). buildTableSelectionContext and contextMenuColumnIds collapsed a range to an open scope when it covered 'every column', comparing against displayColumns.length — which drops hidden columns AND expands workflow groups, so it never meant 'the whole schema'. Selecting every visible column therefore cleared columnIds and the server re-fetched columns the user had hidden. The collapse is removed rather than re-based on a schema count: no count available to a caller describes the schema, and an explicit column list is what the user actually selected. totalColumnCount is gone from the signature. Add-to-chat label undercounted rows. The menu derived its count from selectedRowCount (loaded rows only) while the chip was built from the full rowSel.ids set, so the label could promise fewer rows than were sent. Both now read one addToChatRowIds memo, with the count passed through explicitly since it legitimately differs from the count the delete/run labels use. Monaco line range was off by one. A full-line highlight ends at column 1 of the FOLLOWING line, so endLineNumber named a line that contributed no text — the chip label and the agent prompt both claimed an extra line. The collapse test I added last round asserted the buggy behavior as correct; it now pins the opposite, and fails if the collapse returns.
|
@cursor review |
Cursor Bugbot: last round's fix for the label undercounting rows introduced the opposite error. addToChatRowCount passed the raw selection size, but buildTableSelectionContext caps rowIds at MAX_TABLE_SELECTION_ROWS, so a 2,000-row selection advertised 2,000 while the chip referenced 500 — breaking the same invariant the fix claimed to establish. Routes the count through a chipRowCount helper next to the builder that applies the cap, so the label cannot drift from the payload again. utils.test.ts now asserts the invariant directly rather than the formula: across 1, 42, 500, 750 and 50,000 requested rows, chipRowCount equals the rowIds length the context actually carries. Verified to fail if the cap is dropped.
|
@cursor review |
Cursor Bugbot: the mention sync tests each label with a lookahead that rejects only word characters, so '-', ')' and space all let a shorter label match INSIDE a longer token. '@notes.md:12' matches within '@notes.md:12-40', and '@sales (3 rows)' within '@sales (3 rows) (2)'. Deleting the shorter chip left its context attached, and it was still sent with the message. The label class is pre-existing, but this PR made it routine: line ranges and uniqueContextLabel ordinals generate prefix pairs for any two selections of the same file or table. Fixed at the sync rather than by reshaping labels to dodge the prefix — a label format chosen to avoid a matcher bug would just relocate it. Contexts are now tested longest-label-first, and each matched token is blanked before shorter labels are tested, so every context is judged against text its own token owns. Blanked in place, not removed, so the (^|\s) boundary of whatever sits next to it is preserved; prev order is still what's returned. Shared with the workflow copilot input, so tests cover both directions: the two prefix pairs are dropped when only the longer token remains, both survive when both tokens are present, order is preserved, and trailing punctuation after a mention still keeps its chip.
|
@cursor review |
Cursor Bugbot: areContextsEqual compared only fileId and text for file_selection, while the comment directly above claimed equality was the selected range. A line that occurs twice in a file — a repeated import, a closing brace — highlighted at both places produced identical text, so prepareContextForInsert called the second a duplicate and dropped its chip, even though the labels (notes.md:12 vs notes.md:50) were plainly different. Comparing startLine/endLine as well makes the code match what the comment promised. The rich-markdown editor omits the range, so both sides are undefined there and identical text still dedupes — correct, since two identical passages are genuinely indistinguishable without line numbers. Tests cover both: distinct ranges stay distinct, an exact repeat still dedupes, and the no-line-number path still dedupes. Verified the first fails against text-only equality.
|
@cursor review |
…ll chip Cursor Bugbot: for a gutter select-all the menu count comes from selectedRowCount (capped), but the chip was built by loading exactly MAX_TABLE_SELECTION_ROWS and filtering exclusions AFTER. Any excluded row inside that prefix left the chip short of the advertised count. Third appearance of the same invariant — label vs payload — this time in the select-all path specifically, which the earlier fixes did not touch. Loads the cap plus the exclusion count, which covers the worst case where every exclusion falls inside the prefix, so the filtered result still reaches the cap whenever the table has the rows. Extracted as drainTargetForChip so the compensation is stated and tested rather than an inline arithmetic detail.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 7644b8d. Configure here.
Summary
IDE-style highlight → "Add to chat" for Files and Tables: select a passage in a file (Monaco or the Tiptap markdown editor) or rows/cells in a table, and it becomes a scoped reference chip the Sim agent can read, instead of attaching the whole resource. Cmd+C / Cmd+V works too: copy a selection and paste it into the chat as the same reference.
Adding a selection from a standalone Files/Tables page opens the Chat in the split layout (chat left, resource right) with the chip already in the input; adding it while a resource is already open in Chat drops the chip into the current conversation.
Type of Change
How it works
ChatContextkindsfile_selection/table_selection. File selections carry the literal text + line range inline; table selections carry authoritative row/column ids and are re-fetched server-side by id (never trusting client cell values), scoped to the table + workspace.Blimpicon in the Monaco context menu, the Tiptap bubble menu, and the table context menu.text/x-sim-selectionclipboard type alongsidetext/plain(bubble-phase listener so it survives the editors' ownclearData()), read back by the chat paste handler.Testing
bunx tsc --noEmit: 0 errors. Biome clean.check:api-validationpasses (new context fields live only on the domain validator + theChatContextunion — no boundary violation).process-contents), label/bounds helpers (selection-context), and the clipboard codec round-trip/validation (selection-clipboard, new).Reviewers: focus on
lib/copilot/chat/process-contents.ts(server row re-fetch + bounds) and the merge with staging's nuqs?resource=refactor inhome.tsx.Checklist
🤖 Generated with Claude Code